package org.fujene.communicate.blockwise;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import org.fujene.index.BlockwiseIndexSystem;
import org.fujene.memman.BlockwiseMemoryManager;
import org.fujene.search.BlockwiseSearchSystem;
import org.fujene.structure.Constants;
import org.fujene.structure.ContentInfo;
import org.fujene.structure.DataMatchingException;
import org.fujene.structure.ExtendQueue;
import org.fujene.structure.ExtendResult;
import org.fujene.structure.NodeInfo;
import org.fujene.structure.SearchQueue;
import org.fujene.term.Chopper;
import org.fujene.term.SenChopper;
import org.fujene.toolbox.CompareTool;
import org.msgpack.MessagePackObject;
import org.msgpack.object.ArrayType;
import org.msgpack.object.IntegerType;
import org.msgpack.object.RawType;
import org.msgpack.rpc.Client;
import org.msgpack.rpc.Server;
import org.msgpack.rpc.loop.EventLoop;

public class BlockwisePeerSystem {
	// MemoryManager -> Memory, Disk
	private BlockwiseMemoryManager memman = null;

	public boolean stopFlag = false;

	private ExecutorService execIndex = null;
	private ExecutorService execSearch = null;
	private ExecutorService execNodeLive = null;

	private String directory = null;
	private int blockNum = 0;
	private int blockSize = 0;

	// //////// P2P ///////////
	private static final int PEERPORT = Constants.PEER_PORT;

	private int replication;
	private int indexType;

	private TreeSet<NodeInfo> nodes = null;
	private NodeInfo myNode = null;

	private String[] firstHost = null;

	private Server server = null;

	private Chopper chopper = null;

	private ContentInfo contentInfo = null;

	// //////// Logger ///////////
	private Logger mainLogger = null;

	public void initialize() throws IOException, InterruptedException,
			DataMatchingException {
		nodes = new TreeSet<NodeInfo>();

		// 自分のIPアドレス取得
		Enumeration<NetworkInterface> enulfs = NetworkInterface
				.getNetworkInterfaces();
		if (!enulfs.hasMoreElements())
			return;
		NetworkInterface ni = enulfs.nextElement();

		Enumeration<InetAddress> enuAddr = ni.getInetAddresses();
		if (!enuAddr.hasMoreElements())
			return;
		InetAddress addrInfo = enuAddr.nextElement();
		// TODO: とりあえずIPv4のみ使うことにする ほんとはIPv6使いたいね
		while (!(addrInfo instanceof Inet4Address)) {
			addrInfo = enuAddr.nextElement();
		}

		long hash = calcHash(addrInfo);
		String address = addrInfo.getHostAddress();
		myNode = new NodeInfo(address, hash);
		nodes.add(myNode);
		getInformation();

		if (contentInfo != null)
			contentInfo.optimize();
		else
			contentInfo = new ContentInfo(new String[] { "Content" },
					new String[0]);

		server = new Server(EventLoop.defaultEventLoop());
		server.serve(new BlockwisePeerHandler(mainLogger, this));
		server.listen(PEERPORT);

		new BlockwiseSearchSystem(this, mainLogger);

		new BlockwiseIndexSystem(this, contentInfo.getContentNum(),
				contentInfo.getSupplementNum(), mainLogger);

		memman = new BlockwiseMemoryManager(blockSize, blockNum, directory,
				mainLogger, contentInfo, this);

		execIndex = Executors.newFixedThreadPool(1);

		execSearch = Executors.newFixedThreadPool(1);

		execNodeLive = Executors.newFixedThreadPool(2);

		chopper = new SenChopper();
		execNodeLive.execute(new SurvivingService());
		if (firstHost != null) {
			registerNode();
			callForData();
		}
	}

	private boolean getArgs(String[] args) {
		boolean isEnded = false;
		String settingFile;

		if (/* args.length > 2 || */args.length == 1) {
			System.err
					.println("Usage: PeerSystem [--setting <SettingFile>] [--secondary <IPAddress ...>]");
			return isEnded;
		}
		// デフォルト値
		this.blockNum = 256;
		this.blockSize = 1048576;
		this.directory = "./fujene_data/";
		this.indexType = 1;
		this.replication = 1;
		this.mainLogger = Logger.getLogger("Fujene search engine");
		try {
			FileHandler handle = new FileHandler("fujene_data/fujene.log");
			handle.setFormatter(new SimpleFormatter());
			this.mainLogger.addHandler(handle);
		} catch (SecurityException e1) {
			e1.printStackTrace();
		} catch (Exception e1) {
			e1.printStackTrace();
		}
		this.mainLogger.setLevel(Level.ALL);

		// ローカル
		ArrayList<String> contentName = new ArrayList<String>();
		ArrayList<String> supplementName = new ArrayList<String>();
		int count = 0;

		if (args.length > 1 && args[0].equals("--setting")) {
			settingFile = args[1];

			BufferedReader br = null;
			String input = null;
			try {
				br = new BufferedReader(new FileReader(new File(settingFile)));

				while ((input = br.readLine()) != null) {
					// "#"始まりはコメント行
					if (!input.startsWith("#")) {
						String[] settingArgs = input.split("=");
						if (settingArgs.length >= 2) {
							if (settingArgs[0].equalsIgnoreCase("BlockNum"))
								blockNum = Integer.parseInt(settingArgs[1]);
							else if (settingArgs[0]
									.equalsIgnoreCase("BlockSize"))
								blockSize = Integer.parseInt(settingArgs[1]);
							else if (settingArgs[0]
									.equalsIgnoreCase("Directory"))
								directory = settingArgs[1];
							else if (settingArgs[0]
									.equalsIgnoreCase("PeerMode")) {
								if (settingArgs[1].equalsIgnoreCase("on")) {
									replication = 3;
								}
							} else if (settingArgs[0]
									.equalsIgnoreCase("replication"))
								replication = Integer.parseInt(settingArgs[1]);
							else if (settingArgs[0]
									.equalsIgnoreCase("IndexMode"))
								indexType = Integer.parseInt(settingArgs[1]);
							else if (settingArgs[0]
									.equalsIgnoreCase("LogLevel")) {
								try {
									mainLogger.setLevel(Level
											.parse(settingArgs[1]));
								} catch (Exception e) {
									e.printStackTrace();
									mainLogger
											.severe("Specified log-level does not exist.");
								}
							} else if (settingArgs[0]
									.equalsIgnoreCase("Content")) {
								contentName.add(settingArgs[1]);
							} else if (settingArgs[0]
									.equalsIgnoreCase("Supplement")) {
								supplementName.add(settingArgs[1]);
							} else if (settingArgs[0]
									.equalsIgnoreCase("Connection")) {
								// この時点で紐付け情報を作る
								if (contentInfo == null)
									contentInfo = new ContentInfo(
											(String[]) contentName.toArray(),
											(String[]) supplementName.toArray());

								String[] values = settingArgs[1].split(",");
								int[] indices = new int[values.length];
								for (int i = 0; i < indices.length; i++)
									indices[i] = Integer.parseInt(values[i]);
								contentInfo.connectGraph(count++, indices);
							}
						}
					}

					isEnded = true;
				}
			} catch (FileNotFoundException e) {
				mainLogger
						.severe("Please input appropriate file. Search system will shutdown.");
				return isEnded;
			} catch (IOException e) {
			} finally {
				if (br != null) {
					try {
						br.close();
					} catch (IOException e) {
					}
				}
			}
		} else if (args.length == 0) {
			isEnded = true;
		}

		// セカンダリモード
		if (args.length > 1 && args[0].equals("--secondary")) {
			firstHost = new String[args.length - 1];
			for (int i = 1; i < args.length; i++) {
				firstHost[i - 1] = args[i];
			}
			isEnded = true;
		}

		return isEnded;
	}

	// ローカルで動かしたいときはこっち
	public static void main(String[] args) throws InterruptedException,
			IOException, DataMatchingException {
		BlockwisePeerSystem mainSystem = new BlockwisePeerSystem();

		if (!mainSystem.getArgs(args))
			return;

		mainSystem.initialize();

		mainSystem.console();
	}

	private void console() throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String arg;

		try {
			Thread.sleep(700);
		} catch (InterruptedException ignored) {
		}

		while (true) {
			System.out.print("Fujene > ");
			arg = br.readLine();

			String[] args = arg.split(" ");
			if (args.length > 0) {
				if (args[0].equals("index") || args[0].equals("i")) {
					if (args.length == 1) {
						System.out
								.println("Usage: index ID <Contents> [<Supplements>]");
						System.out.println(contentInfo.getContentNum()
								+ " contents, "
								+ contentInfo.getSupplementNum()
								+ " supplements are needed.");
					} else {
						if (args.length < contentInfo.getContentNum()
								+ contentInfo.getSupplementNum() + 2) {
							System.out.println("Input appropriate contents.");
						} else {
							setNewIndexExtent(new ExtendQueue(
									Arrays.copyOfRange(args, 2,
											2 + contentInfo.getContentNum()),
									Arrays.copyOfRange(
											args,
											2 + contentInfo.getContentNum(),
											2
													+ contentInfo
															.getContentNum()
													+ contentInfo
															.getSupplementNum()),
									Long.parseLong(args[1])));
						}
					}
				} else if (args[0].equals("search") || args[0].equals("s")) {
					if (args.length < 3)
						System.out.println("Specify query.");
					else {
						try {
							ExtendQueue queue;
							int i, j;
							ExtendResult result = setNewSearch(new SearchQueue(
									args[1], args[2], 0, 1000000));
							// ちょっとなら結果を出す
							System.out.println("Result: " + result.getLength()
									+ " articles.");
							if (result.getLength() <= 10) {
								for (i = 0; i < result.getLength(); i++) {
									System.out.print(i + ": ");
									queue = result.getQueue(i);
									for (j = 0; j < queue.contents.length; j++) {
										System.out.print(contentInfo
												.getContentName(j) + " / ");
										System.out.println(queue.contents[j]);
									}
								}
							}
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				} else if (args[0].equals("searchspec") || args[0].equals("p")) {
					if (args.length != 4)
						System.out.println("Specify arguments.");
					else {
						try {
							ExtendResult result = setNewSearch(new SearchQueue(
									args[1], args[2],
									Integer.parseInt(args[3]),
									Integer.parseInt(args[4])));
							int i, j;
							ExtendQueue queue;
							System.out.println("Result total: "
									+ result.getLength());
							if (result.getLength() <= 10) {
								for (i = 0; i < result.getLength(); i++) {
									System.out.println(i + ": ");
									queue = result.getQueue(i);
									for (j = 0; j < queue.contents.length; j++) {
										System.out.print(contentInfo
												.getContentName(j) + " / ");
										System.out.println(queue.contents[j]);
									}
								}
							}
						} catch (Exception ignored) {
							ignored.printStackTrace();
						}
					}
				} else if (args[0].equals("indexcount") || args[0].equals("c")) {
					System.out.println("Total count: " + memman.getCounter());
				} else if (args[0].equals("querycount") || args[0].equals("u")) {
					System.out.println("Total count: "
							+ memman.getSearchcounter());
				} else if (args[0].equals("termcount") || args[0].equals("t")) {
					System.out.println("Term count: " + memman.getTerms());
				} else if (args[0].equals("skipcount") || args[0].equals("k")) {
					System.out.println("Skip ptr. count: "
							+ memman.getSkipPointerLen());
				} else if (args[0].equals("memory") || args[0].equals("m")) {
					memman.getMemoryList();
				} else if (args[0].equals("nodes") || args[0].equals("n")) {
					showNodes();
				} else if (args[0].equals("save") || args[0].equals("v")) {
					saveMemory();
				} else if (args[0].equals("read") || args[0].equals("r")) {
					readMemory();
				} else if (args[0].equals("quit") || args[0].equals("q")) {
					setStopper(true);
				} else {
					System.out
							.println("Command: <i>ndex <args>, <s>earch <args> type, searchs<p>ec <args> from length, <t>ermcount, index<c>ount, q<u>erycount, s<k>ipcount, <m>emory, <r>ead, sa<v>e, <n>ode, <q>uit");
				}
			}
		}
	}

	// 外部からJUnitとかで呼び出すときはこっち
	public void testMain(String[] args) throws InterruptedException,
			IOException, DataMatchingException {
		this.blockNum = 32;
		this.blockSize = 1048576;
		this.directory = "./testDir/";
		this.initialize();
	}

	// 全部の動作を止める操作
	public void setStopper(boolean isStop) {
		stopFlag = isStop;
		execIndex.shutdownNow();
		execSearch.shutdownNow();
		// 無理やりな気がする
		Runtime.getRuntime().exit(0);
	}

	public void setNewIndexExtent(ExtendQueue newIndex) {
		execIndex.execute(new PeerIndexContentsHost(newIndex));
	}

	public class PeerIndexContentsHost implements Runnable {
		private ExtendQueue newIndex;

		public PeerIndexContentsHost(ExtendQueue index) {
			this.newIndex = index;
		}

		@Override
		public void run() {
			memman.index(newIndex, chopper);
		}
	}

	// 大元のsearch窓口
	public ExtendResult setNewSearch(SearchQueue newQuery) throws Exception {
		return memman.search(newQuery, chopper);
	}

	public void readMemory() {
		memman.initializeMemory();
	}

	public void saveMemory() {
		memman.saveMemory();
	}

	// ///////////////////// P2P関連の実装 //////////////////////
	// ノードを返します
	public TreeSet<NodeInfo> getNode() {
		return nodes;
	}

	public ArrayType getInfo() {
		MessagePackObject[] objs = new MessagePackObject[6
				+ contentInfo.getContentNum() + contentInfo.getSupplementNum()];

		objs[0] = IntegerType.create(blockNum);
		objs[1] = IntegerType.create(blockSize);
		objs[2] = IntegerType.create(replication);
		objs[3] = IntegerType.create(indexType);
		objs[4] = IntegerType.create(contentInfo.getContentNum());
		objs[5] = IntegerType.create(contentInfo.getSupplementNum());
		for (int i = 0; i < contentInfo.getContentNum(); i++) {
			objs[6 + i] = RawType.create(contentInfo.getContentName(i));
		}
		for (int i = 0; i < contentInfo.getSupplementNum(); i++) {
			objs[6 + contentInfo.getContentNum() + i] = RawType
					.create(contentInfo.getSupplementName(i));
		}
		return ArrayType.create(objs);
	}

	public int getQueueLength(int type) {
		if (type == 0)
			return ((ThreadPoolExecutor) execIndex).getPoolSize();
		else if (type == 1)
			return ((ThreadPoolExecutor) execSearch).getPoolSize();
		else
			return -1;
	}

	public byte[] callGetFunc(String func, int blockIndex, byte[] argbytes,
			boolean atOnce) {
		NodeInfo callNode = getNodeByIndex(blockIndex);
		byte[] valb = null, tmp = null;
		Client client;
		MessagePackObject val;

		for (int i = 0; i < (nodes.size() < replication ? nodes.size()
				: replication); i++) {
			if (!callNode.equals(myNode)) {
				try {
					client = new Client(callNode.getAddr(), PEERPORT);

					val = client.callApply("callGetFunc", new Object[] {
							RawType.create(func), RawType.create(argbytes) });

					if (val.isRawType()) {
						tmp = val.asByteArray();
						if (tmp != null && valb == null)
							valb = tmp;
					}

					client.close();
					if (atOnce)
						break;
				} catch (UnknownHostException e) {
				}
			}
			callNode = getPrev(callNode);
		}

		return valb;
	}

	public byte[] callSetFunc(String func, int blockIndex, byte[] argbytes,
			boolean atOnce) {
		NodeInfo callNode = getNodeByIndex(blockIndex);
		byte[] valb = null, tmp = null;
		Client client;
		MessagePackObject val;

		for (int i = 0; i < (nodes.size() < replication ? nodes.size()
				: replication); i++) {
			if (!callNode.equals(myNode)) {
				try {
					client = new Client(callNode.getAddr(), PEERPORT);

					val = client.callApply("callSetFunc", new Object[] {
							RawType.create(func), RawType.create(argbytes) });

					if (val.isRawType()) {
						valb = val.asByteArray();
						if (tmp != null && valb == null)
							valb = tmp;
					}

					client.close();
					if (atOnce)
						break;
				} catch (UnknownHostException e) {
				}
			}
			callNode = getPrev(callNode);
		}

		return valb;
	}

	public byte[] invokeGetFunc(String func, byte[] argbytes) {
		return memman.invokeGetFunc(func, argbytes);
	}

	public byte[] invokeSetFunc(String func, byte[] argbytes) {
		return memman.invokeSetFunc(func, argbytes);
	}

	public int getBlockType(int blockIndex) {
		return memman.getBlockType(blockIndex);
	}

	public int getContentType(int blockIndex) {
		return memman.getContentType(blockIndex);
	}

	public byte[] getBlock(int blockIndex) {
		return memman.getBlock(blockIndex);
	}

	public void pushBlock(int blockIndex, int blockType, int contentIndex,
			boolean isAllocate) {
		memman.pushBlock(blockIndex, blockType, contentIndex, isAllocate);
	}

	public void setBlock(int blockIndex, int blockType, int contentIndex) {
		NodeInfo eachNode = null;
		Client client;
		boolean isStoreLocal = false;

		try {
			for (int i = 0; i < nodes.size(); i++) {
				eachNode = getNodeByIndex(i);
				if (!eachNode.equals(myNode)) {
					isStoreLocal = CompareTool.isStoreLocally(blockIndex,
							nodes.size(), i, replication);

					client = new Client(eachNode.getAddr(), PEERPORT);

					client.callApply("setBlockInfo", new Object[] { blockIndex,
							blockType, contentIndex, isStoreLocal });

					client.close();
				}
			}
		} catch (UnknownHostException e) {
			mainLogger.severe("Node cannot found. " + eachNode.getAddr());
		}
	}

	// 自分のハッシュ値を計算する
	private long calcHash(InetAddress addrInfo) {
		// とりあえずマシン名で作る
		long returnHash = addrInfo.getHostName().hashCode() * 0x123456789ABCDEFL;
		returnHash *= returnHash;
		Long.rotateLeft(returnHash, 42);

		return returnHash;
	}

	// n個前(hashが小さい方)のピアを取得
	private NodeInfo getDescentHash(NodeInfo from, int distance) {
		if (from == null)
			return null;
		NodeInfo getNode = from;

		for (int i = 0; i < distance; i++) {
			getNode = getNext(getNode);
		}

		return getNode;
	}

	// n個後(hashが大きい方)のピアを取得
	private NodeInfo getAscentHash(NodeInfo from, int distance) {
		if (from == null)
			return null;
		NodeInfo getNode = from;

		for (int i = 0; i < distance; i++) {
			getNode = getNext(getNode);
		}

		return getNode;
	}

	// 次のピアを取得
	private NodeInfo getNext(NodeInfo from) {
		if (from == null)
			return null;
		TreeSet<NodeInfo> calcNode = new TreeSet<NodeInfo>(nodes);
		boolean isNext = false;

		for (NodeInfo eachNode : calcNode) {
			if (isNext)
				return eachNode;
			if (eachNode.equals(from))
				isNext = true;
		}

		return calcNode.first();
	}

	// 前のピアを取得
	private NodeInfo getPrev(NodeInfo from) {
		if (from == null)
			return null;
		TreeSet<NodeInfo> calcNode = new TreeSet<NodeInfo>(nodes);
		NodeInfo prevNode = null;

		for (NodeInfo eachNode : calcNode) {
			if (eachNode.equals(from))
				if (prevNode == null)
					return calcNode.last();
				else
					return prevNode;
			prevNode = eachNode;
		}

		return null;
	}

	private int getNodeIndex(NodeInfo node) {
		int count = 0;
		for (NodeInfo eachNode : nodes) {
			if (eachNode.equals(node))
				return count;
			count++;
		}

		return -1;
	}

	// 数字でノードを指定
	private NodeInfo getNodeByIndex(int index) {
		int modulo = index % nodes.size();
		int count = 0;

		for (NodeInfo anyNode : nodes) {
			if (count++ == modulo)
				return anyNode;
		}

		return null;
	}

	// ハッシュ直後のピアを取得
	private boolean isLocal(int index) {
		if (getNodeByIndex(index).equals(myNode))
			return true;
		return false;
	}

	// indexable状態を設定
	public void setIndexable(long hash) {
		NodeInfo specified = nodes.ceiling(new NodeInfo(null, hash));

		specified.indexable();
	}

	// indexable状態を設定
	public void setSearchable(long hash) {
		NodeInfo specified = nodes.ceiling(new NodeInfo(null, hash));

		specified.searchable();
	}

	// サークル情報を取得します。
	private void getInformation() {
		if (firstHost != null) {
			Client client = null;
			for (String hosts : firstHost) {
				try {
					client = new Client(hosts, PEERPORT);
					client.setRequestTimeout(3);

					// 情報取得
					MessagePackObject infoobj = client.callApply("getInfos",
							new Object[] {});
					MessagePackObject[] infos;
					if (infoobj.isArrayType()) {
						infos = infoobj.asArray();
						blockNum = infos[0].asInt();
						blockSize = infos[1].asInt();
						replication = infos[2].asInt();
						indexType = infos[3].asInt();
						int contentNum = infos[4].asInt();
						String[] strcontent = new String[contentNum];
						int supplementNum = infos[5].asInt();
						String[] strsupp = new String[supplementNum];
						for (int i = 0; i < contentNum; i++) {
							strcontent[i] = infos[6 + i].asString();
						}
						for (int i = 0; i < supplementNum; i++) {
							strsupp[i] = infos[6 + contentNum + i].asString();
						}
						contentInfo = new ContentInfo(strcontent, strsupp);
					} else
						return;

					break;
				} catch (UnknownHostException e) {
					continue;
				}
			}
		}
	}

	// 追加操作を行います。
	public void addReplication(NodeInfo newPeer) {
		// サークルに追加
		nodes.add(newPeer);

		try {
			callForData();
		} catch (DataMatchingException e) {
		} catch (IOException e) {
		}
	}

	private void showNodes() {
		TreeSet<NodeInfo> showNode = new TreeSet<NodeInfo>(nodes);
		int count = 0;

		for (NodeInfo eachNode : showNode) {

			System.out.println((count++) + ": Hash-"
					+ String.format("0x%016x", eachNode.getHash())
					+ " Address-" + eachNode.getAddr());
		}
	}

	// ノードを登録します。
	private void registerNode() {
		MessagePackObject obj;
		Client client;
		MessagePackObject[] objs;

		for (String host : firstHost) {
			// ノードリスト要求
			try {
				client = new Client(host, PEERPORT);
				client.setRequestTimeout(5);

				obj = client.callApply("getNodeList", new Object[] {});
				if (obj.isArrayType()) {
					objs = obj.asArray();
					for (int i = 0; i < objs.length; i += 2)
						nodes.add(new NodeInfo(objs[i].asString(), objs[i + 1]
								.asLong()));
				} else {
					// やり直し
					client.close();
					nodes.clear();
					continue;
				}
				// ブロックリスト受信
				obj = client.callApply("blockList", new Object[] {});
				if (obj.isArrayType()) {
					objs = obj.asArray();
					for (int i = 0; i < objs.length / 2; i++) {
						memman.registerBlock(i, objs[2 * i + 1].asInt(),
								objs[2 * i].asInt());
					}
				}

				client.close();
			} catch (UnknownHostException e) {
				continue;
			}

			// 現時点でのノードリストを使って自分のことを送信
			TreeSet<NodeInfo> currentNode = new TreeSet<NodeInfo>(nodes);
			Client nodeClient;
			System.out.println("My node: " + myNode.getAddr());
			for (NodeInfo eachNode : currentNode) {
				System.out.println("Node: " + eachNode.getAddr());
				if (!eachNode.equals(myNode)) {
					try {
						nodeClient = new Client(eachNode.getAddr(), PEERPORT);
						nodeClient.setRequestTimeout(5);

						nodeClient
								.callApply(
										"register",
										new Object[] {
												RawType.create(myNode.getAddr()),
												IntegerType.create(myNode
														.getHash()), });

						nodeClient.close();
					} catch (UnknownHostException e) {
					}
				}
			}
			break;
		}
	}

	// データ要求をします。
	private void callForData() throws DataMatchingException, IOException {
		MessagePackObject obj;
		MessagePackObject[] objs;
		Client client;
		int modulo;
		int i;
		int copyOffset;
		int nodelen = nodes.size();
		NodeInfo node;

		// 範囲の計算
		if (nodes.size() <= replication)
			modulo = 0;
		else
			modulo = getNodeIndex(myNode);

		mainLogger.finest("Modulo: " + modulo);

		for (i = 0, copyOffset = 1; i < memman.getBlockAmount(); i += nodelen, copyOffset++) {
			if (copyOffset == nodelen)
				copyOffset = 1;
			for (int j = 0; j < nodelen && i + j < memman.getBlockAmount(); j++) {
				if ((j >= modulo && j < modulo + replication)
						|| (j < ((modulo + replication) - nodelen))) {
					if (!memman.existsLocal(i + j)) {
						mainLogger.finest("Copy " + (i + j));

						node = getAscentHash(getNodeByIndex(0), copyOffset);
						// if (!node.isIndexable()) {
						// // 新しいノードは飛ばす
						// if (++copyOffset == nodelen)
						// copyOffset = 1;
						// node = getAscentHash(myNode, copyOffset);
						// }
						client = new Client(node.getAddr(), PEERPORT);

						obj = client.callApply("getBlock",
								new Object[] { (i + j) });
						if (obj.isArrayType()) {
							objs = obj.asArray();
							memman.setBlock(objs[0].asByteArray(), i + j,
									objs[1].asInt(), objs[2].asInt());
						}

						client.close();
					}
				} else {
					if (memman.existsLocal(i + j)) {
						mainLogger.finest("Delete " + (i + j));
						memman.deleteBlock(i + j);
					}
				}
			}
		}

		// TODO: コピー終了時点でindexableにする

	}

	// 削除操作を行います。
	public void deleteReplication(NodeInfo delPeer)
			throws DataMatchingException, IOException {
		MessagePackObject obj;
		MessagePackObject[] objs;
		Client client;
		int modulo;
		int i;
		int copyOffset;
		int nodelen = nodes.size() - 1;
		NodeInfo node;

		delPeer.setDead();

		// 範囲の計算
		if (nodes.size() <= replication)
			modulo = 0;
		else
			modulo = getNodeIndex(myNode);

		for (i = 0, copyOffset = 1; i < memman.getBlockAmount(); i += nodelen, copyOffset++) {
			if (copyOffset == nodelen)
				copyOffset = 1;
			for (int j = 0; j < nodelen; j++) {
				if (!memman.existsLocal(i + j)) {
					node = getDescentHash(getNodeByIndex(0), copyOffset);
					if (node.equals(delPeer)) {
						// 死亡したノードは飛ばす
						node = getPrev(node);
					}
					client = new Client(node.getAddr(), PEERPORT);

					obj = client
							.callApply("getBlock", new Object[] { (i + j) });
					if (obj.isArrayType()) {
						objs = obj.asArray();
						memman.setBlock(objs[0].asByteArray(), i + j,
								objs[1].asInt(), objs[2].asInt());
					}

					client.close();
				}
			}
		}

		// ノードテーブルから削除する
		nodes.remove(delPeer);
	}

	public boolean storeLocally(int blockIndex) {
		int modulo;
		int nodelen = nodes.size();

		// 範囲の計算
		if (nodes.size() <= replication)
			return true;
		else
			modulo = getNodeIndex(myNode);

		if (((blockIndex % nodelen) >= modulo && (blockIndex % nodelen) < modulo + replication)
				|| ((blockIndex % nodelen) < ((modulo + replication) - nodelen)))
			return true;
		return false;
	}

	public int[] getBlockList() {
		Integer[] blockArray = memman.getBlockList();
		int[] values = new int[blockArray.length];

		for (int i = 0; i < blockArray.length; i++) {
			values[i] = blockArray[i].intValue();
		}

		return values;
	}

	/**
	 * ノードの生存確認をします。
	 */
	public class SurvivingService implements Runnable {
		public SurvivingService() {
		}

		@Override
		public void run() {
			while (true) {
				Client client;
				MessagePackObject result;
				TreeSet<NodeInfo> checker = new TreeSet<NodeInfo>(nodes);

				for (NodeInfo eachInfo : checker) {
					if (!eachInfo.equals(myNode)) {
						try {
							client = new Client(eachInfo.getAddr(), PEERPORT);
							// 3秒だけ待ってやる
							client.setRequestTimeout(3);

							result = client
									.callApply("isLive", new Object[] {});
							if (!result.isRawType()) {
								// クライアント死亡と推定
								deleteReplication(eachInfo);
							} else if (stopFlag) {
								// 自分が死ぬことを通知
								client.callApply("delete", new Object[] {});
							}
						} catch (Exception e) {
							mainLogger.info("Node " + eachInfo.getAddr()
									+ " seems to be dead.");
							// クライアント死亡と推定
							try {
								
								deleteReplication(eachInfo);
							} catch (DataMatchingException e1) {
							} catch (IOException e1) {
							}
						}
					}
				}

				try {
					if (stopFlag) {
						break;
					}
					TimeUnit.SECONDS.sleep(60);
				} catch (Exception e) {
					continue;
				}
			}
		}
	}
}
